#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include "coresight.h"
#include <asm/sysreg.h>
#include <asm-generic/delay.h>
#include "coresight_priv.h"

#define CORESIGHT_FILE_DEBUG "coresight-trbe"

u64 trace_mem_base = 0;

static int coresight_timeout1(int position, int value)
{
	int i;
	u32 val;

	for (i = 100; i > 0; i--) {
		val = read_ete_sysreg(TRCSTATR);
		/* waiting on the bit to go from 0 to 1 */
		if (value) {
			if (val & BIT(position))
				return 0;
			/* waiting on the bit to go from 1 to 0 */
		} else {
			if (!(val & BIT(position)))
				return 0;
		}

		/*
		 * Delay is arbitrary - the specification doesn't say how long
		 * we are expected to wait.  Extra check required to make sure
		 * we don't wait needlessly on the last iteration.
		 */
		if (i - 1)
			udelay(1);
	}

	return -EAGAIN;
}

static void trbe_flush_regs(TrbeRegs *trbe)
{
	write_sysreg_s(CORESIGHT_REG(trbe->trbptr), SYS_TRBPTR_EL1);
	write_sysreg_s(CORESIGHT_REG(trbe->trbbaser), SYS_TRBBASER_EL1);
	write_sysreg_s(CORESIGHT_REG(trbe->trbsr), SYS_TRBSR_EL1);
	isb();
	write_sysreg_s(CORESIGHT_REG(trbe->trblimitr), SYS_TRBLIMITR_EL1);
	isb();
	TRBE_REG_KERNEL_SHOW(SYS_TRBPTR_EL1);
	TRBE_REG_KERNEL_SHOW(SYS_TRBBASER_EL1);
	TRBE_REG_KERNEL_SHOW(SYS_TRBSR_EL1);
	TRBE_REG_KERNEL_SHOW(SYS_TRBLIMITR_EL1);
}

static void trbe_save_regs(TrbeRegs *trbe)
{
	CORESIGHT_REG(trbe->trbptr) = read_sysreg_s(SYS_TRBPTR_EL1);
	CORESIGHT_REG(trbe->trbbaser) = read_sysreg_s(SYS_TRBBASER_EL1);
	CORESIGHT_REG(trbe->trbsr) = read_sysreg_s(SYS_TRBSR_EL1);
	CORESIGHT_REG(trbe->trblimitr) = read_sysreg_s(SYS_TRBLIMITR_EL1);
}

static void trbe_stop(void *arg)
{
	CoresightDesc *desc = arg;
	TrbeRegs tmp;

	desc->trbe.trblimitr.enable = 0;
	write_sysreg_s(CORESIGHT_REG(desc->trbe.trblimitr), SYS_TRBLIMITR_EL1);
	isb();

	switch (desc->flag) {
	case CORESIGHT_SUSPEND:
		trbe_save_regs(&desc->trbe);
		break;
	case CORESIGHT_STOP:
		memset(&tmp, 0, sizeof(tmp));
		trbe_flush_regs(&tmp);
		break;
	default:
		break;
	}
}

static void trbe_config(TrbeRegs *trbe)
{
	u64 trbe_limiter = 0;
	u64 cpu = smp_processor_id();

	memset(trbe, 0, sizeof(*trbe));
	trbe_limiter = trace_mem_base + TRBE_MEM_PERCPU_SIZE * cpu;
	memset((void *)trbe_limiter, 0, TRBE_MEM_PERCPU_SIZE);
	trbe->trbbaser.base = trbe_limiter >> 12;
	trbe->trbptr.ptr = trbe_limiter;
	trbe->trblimitr.limit = (trbe_limiter + TRBE_MEM_PERCPU_SIZE) >> 12;
	trbe->trblimitr.fm = TRBLIMITR_FILL_MODE_MASK;
	trbe->trblimitr.tm = TRBLIMITR_TRIG_MODE_MASK;
	trbe->trblimitr.enable = TRBLIMITR_ENABLE;
}
static void trbe_start(void *info)
{
	CoresightDesc *desc = info;
	TrbeRegs *trbe = &desc->trbe;

	if (desc->flag == CORESIGHT_START)
		trbe_config(trbe);
	trbe->trblimitr.enable = 1;
	trbe_flush_regs(trbe);
}

static void ete_flush_regs(EteRegs *ete)
{
	write_ete_sysreg(CORESIGHT_REG(ete->trcconfigr), TRCCONFIGR);
	write_ete_sysreg(CORESIGHT_REG(ete->trcsyncpr), TRCSYNCPR);
	write_ete_sysreg(CORESIGHT_REG(ete->trctraceidr), TRCTRACEIDR);
	write_ete_sysreg(CORESIGHT_REG(ete->trcvictlr), TRCVICTLR);
	write_ete_sysreg(CORESIGHT_REG(ete->trcccctlr), TRCCCCTLR);
	write_ete_sysreg(CORESIGHT_REG(ete->trceventctl0r), TRCEVENTCTL0R);
	write_ete_sysreg(CORESIGHT_REG(ete->trceventctl1r), TRCEVENTCTL1R);
	write_ete_sysreg(CORESIGHT_REG(ete->trctsctlr), TRCTSCTLR);
	write_ete_sysreg(CORESIGHT_REG(ete->trcviiectlr), TRCVIIECTLR);
	write_ete_sysreg(CORESIGHT_REG(ete->trcvissctlr), TRCVISSCTLR);
	write_ete_sysreg(CORESIGHT_REG(ete->trcbbctlr), TRCBBCTLR);
	write_ete_sysreg(CORESIGHT_REG(ete->trcrsr), TRCRSR);
	isb();
}

static void ete_config(EteRegs *ete)
{
	//config
	ete->trcconfigr.vmidopt = 1;
	ete->trcconfigr.rs = 1;
	ete->trcconfigr.ts = 1;
	ete->trcconfigr.vmid = 1;
	ete->trcconfigr.cid = 1;
	ete->trcconfigr.cci = 0;
	ete->trcconfigr.bb = 1;
	ete->trcconfigr.res3 = 0;

	ete->trcsyncpr.period = 0xc;
	ete->trctraceidr.traceid = 0x10;

	ete->trcvictlr.exlevel_ns_el0 = 1;
	ete->trcvictlr.exlevel_s_el3 = 1;
	ete->trcvictlr.exlevel_s_el2 = 1;
	ete->trcvictlr.exlevel_s_el1 = 1;
	ete->trcvictlr.exlevel_s_el0 = 1;
	ete->trcvictlr.ssstatus = 1;
	ete->trcvictlr.event_sel = 1;

	ete->trcccctlr.threshold = 0;
	memset(&ete->trceventctl0r, 0, sizeof(ete->trceventctl0r));
	memset(&ete->trceventctl1r, 0, sizeof(ete->trceventctl1r));

	ete->trctsctlr.event_sel = 0x2;
	memset(&ete->trcviiectlr, 0, sizeof(ete->trcviiectlr));
	memset(&ete->trcvissctlr, 0, sizeof(ete->trcvissctlr));
	memset(&ete->trcbbctlr, 0, sizeof(ete->trcbbctlr));
	memset(&ete->trcrsr, 0, sizeof(ete->trcrsr));
}

static void ete_start(void *arg)
{
	CoresightDesc *desc = arg;
	EteRegs *ete = &desc->ete;

	//unlock
	write_sysreg_s(0, SYS_OSLAR_EL1);
	isb();
	write_ete_sysreg(0, TRCPRGCTLR);
	isb();
	if (coresight_timeout1(TRCSTATR_IDLE_BIT, 1))
		pr_err("timeout while waiting for Idle Trace Status\n");

	memset(ete, 0, sizeof(*ete));
	ete_config(ete);
	ete_flush_regs(ete);

	write_sysreg_s(TRFCR_ELx_TS_VIRTUAL | TRFCR_ELx_ExTRE | TRFCR_EL2_CX,
		       SYS_TRFCR_EL1);
	//enable
	write_ete_sysreg(1, TRCPRGCTLR);
	isb();
	if (coresight_timeout1(TRCSTATR_IDLE_BIT, 0))
		pr_err("timeout while waiting for Run Trace Status\n");
	dsb(sy);
	isb();
}

static void ete_stop(void *arg)
{
	write_ete_sysreg(0, TRCPRGCTLR);
	if (coresight_timeout1(TRCSTATR_IDLE_BIT, 1))
		pr_err("timeout while waiting for Idle Trace Status\n");
}

void coresight_start(void *arg)
{
	trbe_start(arg);
	ete_start(arg);
}

void coresight_stop(void *arg)
{
	trbe_stop(arg);
	ete_stop(arg);
}

void trbe_show_regs(struct seq_file *m)
{
	TRBE_REG_SHOW(m, SYS_TRBPTR_EL1);
	TRBE_REG_SHOW(m, SYS_TRBBASER_EL1);
	TRBE_REG_SHOW(m, SYS_TRBSR_EL1);
	TRBE_REG_SHOW(m, SYS_TRBLIMITR_EL1);
}

static int coresight_debug_show(struct seq_file *m, void *v)
{
	seq_printf(m,
		   "echo on > /sys/kernel/debug/%s, open ete & trbe trace.\n",
		   CORESIGHT_FILE_DEBUG);
	seq_printf(m,
		   "echo off > /sys/kernel/debug/%s, close ete & trbe trace.\n",
		   CORESIGHT_FILE_DEBUG);
	trbe_show_regs(m);

	return 0;
}

static int coresight_debug_open(struct inode *inode, struct file *file)
{
	return single_open(file, coresight_debug_show, inode->i_private);
}

static ssize_t coresight_debug_write(struct file *file, const char __user *buf,
				     size_t count, loff_t *ppos)
{
	char cmd[10];

	if (copy_from_user(cmd, buf, min(count, sizeof(cmd))))
		return -EFAULT;

	if (!strncmp(cmd, "on", 2)) {
		coresight_enable_cpus(cpu_online_mask);
	} else if (!strncmp(cmd, "off", 3)) {
		coresight_disable_cpus(cpu_online_mask);
	}

	return count;
}

static const struct file_operations coresight_debug_ops = {
	.open = coresight_debug_open,
	.write = coresight_debug_write,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};

static struct dentry *coresight_debug_file = NULL;

static int coresight_debug_init(void)
{
	coresight_debug_file = debugfs_create_file(CORESIGHT_FILE_DEBUG,
						   S_IWUSR, NULL, NULL,
						   &coresight_debug_ops);
	if (!coresight_debug_file)
		return -1;
	return 0;
}

static void coresight_debug_exit(void)
{
	if (coresight_debug_file)
		debugfs_remove(coresight_debug_file);
}

static int __init coresight_init(void)
{
#ifdef CONFIG_CORESIGHT
	return -1;
#endif
	pr_err("cpu_possible_mask: %x\n", cpu_possible_mask->bits[0]);
	pr_err("cpu_online_mask: %x\n", cpu_online_mask->bits[0]);
	trace_mem_base = (u64)ioremap_wc(
		TRBE_MEM_BASE, (size_t)TRBE_MEM_PERCPU_SIZE * nr_cpu_ids);
	if (coresight_smp_init()) {
		pr_err("failed to init smp\n");
		iounmap((void *)trace_mem_base);
		return -1;
	}
	if (coresight_debug_init()) {
		pr_err("failed to create debugfs file\n");
		iounmap((void *)trace_mem_base);
		coresight_smp_exit();
		return -1;
	}
	return 0;
}
module_init(coresight_init);

static void __exit coresight_exit(void)
{
#ifdef CONFIG_CORESIGHT
	return;
#endif
	coresight_debug_exit();
	coresight_smp_exit();
	iounmap((void *)trace_mem_base);
}
module_exit(coresight_exit);

MODULE_AUTHOR("zheng wenfeng");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("core trace");
MODULE_ALIAS("core trace module");